#include "stdafx.h"
#include "MaxcDownloader.h"
#include "StatusException.h"
#include "URIParse.h"
#include <mutex>
#include <cstring>
extern std::mutex fileLock;
extern int threadNum;
extern std::string hostname;
extern std::string path;
extern std::string filepath;
extern std::string UA;
extern unsigned int fileSize;
namespace maxc
{
	namespace down
	{
		Download::Download(maxc::_asio::io_service& ios) :partStart(0),partEnd(0),ios(ios), file(filepath, std::ios::binary | std::ios::out), HTTP(ios, hostname.c_str())
		{
			httpReqHeader = httpReqHeader
				+ strHeaderPair("Host", hostname)
				+ strHeaderPair("Accept", "*/*,application/metalink4+xml,application/metalink+xml")
				+ strHeaderPair("Connection", "close")
				+ strHeaderPair("User-Agent", UA);
		}

		Download::Download(maxc::_asio::io_service & ios, unsigned int st, unsigned int ed) :partStart(st),partEnd(ed),ios(ios),file(filepath.c_str(), std::ios::binary | std::ios::out | std::ios::in), HTTP(ios, hostname.c_str())
		{
			httpReqHeader = httpReqHeader
				+ strHeaderPair("Host", hostname)
				+ strHeaderPair("Accept", "*/*,application/metalink4+xml,application/metalink+xml")
				+ strHeaderPair("Connection", "close")
				+ strHeaderPair("User-Agent", UA);
			SetRanges(st, ed);
		}

		Download& Download::SetRanges(unsigned int start, unsigned int end)
		{
			auto toString = [](std::string& a, unsigned int b) {std::ostringstream oss; oss << b; a = oss.str(); };
			std::string st, ed;
			toString(st, start);
			toString(ed, end);
			httpReqHeader += header::HTTPRequestHeader::strHeaderMarkPair("Range", "bytes="+st + '-' + ed);
			return *this;
		}
		Download& Download::SetCookie(std::string cookie)
		{
			httpReqHeader["Cookie"] = cookie;
			return *this;
		}

		Download & Download::ResetFilePath(const char * filepath)
		{
			file.close();
			file.open(filepath, std::ios::binary | std::ios::out);
			return *this;
		}

		void Download::StartDownload()  //single thread
		{
			HTTP::Connect();
			try
			{
				HTTP::Requests(httpReqHeader.SetGetHeader(path.c_str()));
			}
			catch (maxc::StatusException::S302Exception)
			{
				//uri parse
				URIParse uri(mhttpResHeader.m302Location);
				uri.Parse();
				hostname = uri.hostname;
				path = uri.path;
				Download my302Down{ ios };
				my302Down.MTDownload();
				return;
			}
			catch (maxc::StatusException::ServerResponseException& e)
			{
				std::cout << e.what();
				return;
			}
			auto& response = mhttpResHeader.mResponse;
			HTTP::Readall();
			file << &response;
		}

		void Download::MTDownload()
		{
			if (threadNum==1)
			{
				StartDownload();
				return;
			}
			HTTP::Connect();
			try
			{
				HTTP::Requests(httpReqHeader.SetGetHeader(path.c_str()));
			}
			catch (maxc::StatusException::S302Exception)
			{
				//uri parse
				URIParse uri(mhttpResHeader.m302Location);
				uri.Parse();
				hostname = uri.hostname;
				path = uri.path;
				Download my302Down{ ios };
				my302Down.MTDownload();
				return;
			}
			catch (maxc::StatusException::ServerResponseException& e)
			{
				std::cout << e.what();
				return;
			}
			auto& response = mhttpResHeader.mResponse;
			FileAlloc();
			auto downSize = mContentLength / threadNum;
			std::unique_ptr<std::vector<std::thread>> threads{ new std::vector<std::thread>() };
			unsigned int st = 0;
			unsigned int ed = 0;
			auto downThread = [&] {Download t{ ios,st,ed }; t.StartDownloadMT(); t.WriteToFileMT(); };
			for (int i = 0; i <threadNum-1; i++)
			{
				st = i*downSize;
				ed = (i + 1)*downSize - 1;
				threads->push_back(std::thread(downThread));
			}
			st = (threadNum - 1)*downSize;
			ed = mContentLength - 1;
			threads->push_back(std::thread(downThread));
			for (auto &t:*threads)
			{
				t.join();
			}
		}

		//void Download::RBDownload() //Resume Broken Download
		//{
		//	if (threadNum == 1)
		//	{
		//		StartDownloadMT();
		//		WriteToFileMT();
		//		return;
		//	}
		//	HTTP::Connect();
		//	try
		//	{
		//		HTTP::Requests(httpReqHeader.SetGetHeader(path.c_str()));
		//	}
		//	catch (maxc::StatusException::S302Exception)
		//	{
		//		//uri parse
		//		URIParse uri(mhttpResHeader.m302Location);
		//		uri.Parse();
		//		hostname = uri.hostname;
		//		path = uri.path;
		//		Download my302Down{ ios };
		//		my302Down.RBDownload();
		//		return;
		//	}
		//	catch (maxc::StatusException::ServerResponseException& e)
		//	{
		//		std::cout << e.what();
		//		return;
		//	}
		//	auto& response = mhttpResHeader.mResponse;
		//	auto downSize = mContentLength / threadNum;
		//	std::unique_ptr<std::vector<std::thread>> threads{ new std::vector<std::thread>() };
		//	unsigned int st = 0;
		//	unsigned int ed = 0;
		//	auto downThread = [&] {Download t{ ios,st,ed }; t.StartDownloadMT(); t.WriteToFileMT(); };
		//	for (int i = 0; i <threadNum - 1; i++)
		//	{
		//		st =partStart+ i*downSize;
		//		ed = partStart+ (i + 1)*downSize - 1;
		//		threads->push_back(std::thread(downThread));
		//	}
		//	st = partStart+ (threadNum - 1)*downSize;
		//	ed =partStart+ mContentLength - 1;
		//	threads->push_back(std::thread(downThread));
		//	for (auto &t : *threads)
		//	{
		//		t.join();
		//	}
		//}

		void Download::StartDownloadMT()
		{
			HTTP::Connect();
			HTTP::Requests(httpReqHeader.SetGetHeader(path.c_str()));
			HTTP::Readall();
		}

		void Download::WriteToFileMT()
		{
			fileLock.lock();
			file.seekp(partStart,std::ios::beg);
			auto& response = mhttpResHeader.mResponse;
			file << &response;
			file.close();
			fileLock.unlock();
		}

		unsigned int Download::GetFileSize()
		{
			return mContentLength;
		}

		void Download::FileAlloc()
		{
			fileSize = mContentLength;
			if (mContentLength<536870912)
			{
				char *alloc = new char[mContentLength];
				file.write(alloc, mContentLength);
				delete[] alloc;
			}
			else
			{
				char *alloc = new char[536870912];
				for (size_t i = 0; i < mContentLength/ 536870912; i++)
				{
					file.write(alloc, 536870912);
				}
				file.write(alloc, mContentLength % 536870912);
				delete[] alloc;
			}			
			file.close();
		}
	}
}